﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using AbstraX.ServerInterfaces;
using System.ServiceModel.DomainServices.Server;
using System.ComponentModel.DataAnnotations;
using AbstraX.XPathBuilder;
using System.Runtime.Serialization;
using MvvmTreeView;
using System.Diagnostics;
using AbstraX.Templates;
using AbstraX;
using AbstraX.TypeMappings;
using System.Collections;
using XPathParser;
using AbstraX.Contracts;
using System.Xml.Schema;
using Utils;

namespace XmlSchemaProvider.Web.Entities
{
    [DataContract, NodeImage("XmlSchemaProvider.Web.Images.Element.png"), DebuggerDisplay("{ DebugInfo }"), ClientCodeGeneration(typeof(AbstraXClientInterfaceGenerator))]
    public class SchemaElement : SchemaBase, IElement, ISchemaParent
    {
        private string name;
        private XmlNode node;
        private string queryWhereProperty;
        private object queryWhereValue;
        private ISchemaParent parent;
        private bool isEnum;
        private string xPathExpression;
        private string exportedType;
        [Exclude()]
        public XmlNode ReferencedFrom { get; private set; }
        [Exclude()]
        public SchemaElement BasedOn { get; private set; }
        protected IProviderEntityService providerEntityService;

        // TODO - we are hard-coding namespaces, a no-no

        public SchemaElement()
        {
        }

        public SchemaElement(XmlDocument edmxDocument, XmlNamespaceManager namespaceManager, string name, XmlNode node, ISchemaParent parent, string folderKeyPair) : this(edmxDocument, namespaceManager, name, node, parent)
        {
            this.FolderKeyPair = folderKeyPair;
        }

        public SchemaElement(XmlDocument edmxDocument, XmlNamespaceManager namespaceManager, string name, XmlNode node, ISchemaParent parent, XmlNode referencedFrom) : this(edmxDocument, namespaceManager, name, node, parent)
        {
            this.ReferencedFrom = referencedFrom;
        }

        public SchemaElement(XmlDocument edmxDocument, XmlNamespaceManager namespaceManager, string name, XmlNode node, ISchemaParent parent)
        {
            this.SchemaDocument = edmxDocument;
            this.NamespaceManager = namespaceManager;
            this.name = name;
            this.node = node;
            this.parent = parent;

            xPathExpression = this.GetLocation();

            if (node.ChildNodes.Count > 0)
            {
                isEnum = node.ChildNodes.Cast<XmlNode>().Any(n1 => n1.LocalName == "simpleType" &&
                    n1.ChildNodes.Cast<XmlNode>().Any(n2 => n2.LocalName == "restriction" &&
                    n2.ChildNodes.Cast<XmlNode>().Any(n3 => n3.LocalName == "enumeration")));
            }

            if (node.Attributes.Cast<XmlAttribute>().Any(a => a.LocalName == "type"))
            {
                XmlTypeCode typeCode;
                var typeAttr = node.Attributes.Cast<XmlAttribute>().Single(a => a.Name == "type");
                var type = typeAttr.Value;

                if (type.IndexOf(":") != -1)
                {
                    type = type.RemoveNamespace();
                }

                if (!Enum.TryParse<XmlTypeCode>(type, true, out typeCode))
                {
                    exportedType = type;
                }
            }

            providerEntityService = ((IBase)parent).ProviderEntityService;
        }

        [Exclude]
        IProviderEntityService IBase.ProviderEntityService
        {
            get
            {
                return providerEntityService;
            }
        }

        [Exclude()]
        public XmlNode Node
        {
            get 
            { 
                return node; 
            }
        }

        [Association("Element_Attributes", "ID", "ParentID")]
        public List<SchemaAttribute> ElementAttributes
        {
            get
            {
                var list = (XmlNodeList)null;
                var properties = new List<SchemaAttribute>();
                var complexContent = (XmlNode)null;
                var simpleContent = (XmlNode)null;

                if (exportedType != null)
                {
                    var typeNode = this.SchemaDocument.SelectSingleNode("/xs:schema/xs:complexType[@name='" + exportedType + "']", this.NamespaceManager);

                    complexContent = typeNode.SelectSingleNode("xs:complexContent/xs:extension", this.NamespaceManager);
                    simpleContent = typeNode.SelectSingleNode("xs:simpleContent/xs:extension", this.NamespaceManager);

                    if (complexContent != null)
                    {
                        list = complexContent.SelectNodes("xs:attribute | xs:attributeGroup", this.NamespaceManager);
                    }
                    else if (simpleContent != null)
                    {
                        // todo 

                        Debugger.Break();
                    }
                    else
                    {
                        list = typeNode.SelectNodes("child::*/xs:attribute", this.NamespaceManager);
                    }
                }
                else
                {
                    list = node.SelectNodes("xs:complexType/xs:attribute", this.NamespaceManager);

                    complexContent = node.SelectSingleNode("xs:complexType/xs:complexContent/xs:extension", this.NamespaceManager);
                    simpleContent = node.SelectSingleNode("xs:complexType/xs:simpleContent/xs:extension", this.NamespaceManager);
                }

                foreach (XmlNode childNode in list)
                {
                    if (childNode.LocalName == "attributeGroup")
                    {


                    }
                    else
                    {
                        var name = childNode.Attributes["name"].Value;

                        properties.Add(new SchemaAttribute(this.SchemaDocument, this.NamespaceManager, name, childNode, this));
                    }
                }

                if (node.Attributes.Cast<XmlAttribute>().Any(a => a.LocalName == "type"))
                {
                    var name = "InnerValue";
                    XmlTypeCode typeCode;
                    var typeAttr = node.Attributes.Cast<XmlAttribute>().Single(a => a.LocalName == "type");
                    var type = typeAttr.Value;

                    if (type.IndexOf(":") != -1)
                    {
                        type = type.RemoveNamespace();
                    }

                    if (Enum.TryParse<XmlTypeCode>(type, true, out typeCode))
                    {
                        properties.Add(new SchemaAttribute(this.SchemaDocument, this.NamespaceManager, name, typeAttr, this));
                    }
                }
                else
                {
                    var restrictionElem = node.SelectSingleNode("xs:simpleType/xs:restriction", this.NamespaceManager);

                    if (restrictionElem != null)
                    {
                        var _base = restrictionElem.Attributes.Cast<XmlAttribute>().Where(a => a.LocalName == "base").Single();
                        var name = "InnerValue";

                        properties.Add(new SchemaAttribute(this.SchemaDocument, this.NamespaceManager, name, _base, this));
                    }
                }

                if (queryWhereProperty != null && queryWhereProperty == "SchemaAttribute")
                {
                    properties = properties.Where(p => p.Name == (string)queryWhereValue).ToList();
                }

                return properties;
            }
        }

        public IEnumerable<IAttribute> Attributes
        {
            get 
            {
                var attributes = this.ElementAttributes.AsQueryable().Cast<IAttribute>().Select(e => e);

                return attributes;
            }
        }

        [Association("Element_ChildElements", "ID", "ParentID")]
        public List<SchemaElement> Elements
        {
            get
            {
                var elements = new List<SchemaElement>();
                var list = (List<XmlNode>)null;
                var complexContent = (XmlNode)null;
                var simpleContent = (XmlNode)null;
                var baseType = (SchemaElement)null;

                if (exportedType != null)
                {
                    var typeNode = this.SchemaDocument.SelectSingleNode("/xs:schema/xs:complexType[@name='" + exportedType + "']", this.NamespaceManager);

                    complexContent = typeNode.SelectSingleNode("xs:complexContent/xs:extension", this.NamespaceManager);
                    simpleContent = typeNode.SelectSingleNode("xs:simpleContent/xs:extension", this.NamespaceManager);

                    if (complexContent != null)
                    {
                        var baseTypeName = complexContent.Attributes["base"].Value;
                        var baseTypeNode = this.SchemaDocument.SelectSingleNode("/xs:schema/xs:complexType[@name='" + baseTypeName + "']", this.NamespaceManager);

                        baseType = new SchemaElement(this.SchemaDocument, this.NamespaceManager, baseTypeName, baseTypeNode, this, "Base Type;XmlSchemaProvider.Web.Images.Folder.png");

                        this.BasedOn = baseType;

                        list = complexContent.SelectNodes("xs:element | xs:group", this.NamespaceManager).Cast<XmlNode>().ToList();

                        var baseTypeList = baseTypeNode.SelectNodes("child::*/xs:element | child::*/xs:group", this.NamespaceManager).Cast<XmlNode>().ToList();

                        list.AddRange(baseTypeList);
                    }
                    else if (simpleContent != null)
                    {
                        // todo 

                        Debugger.Break();
                    }
                    else
                    {
                        list = typeNode.SelectNodes("child::*/xs:element | child::*/xs:group", this.NamespaceManager).Cast<XmlNode>().ToList();
                    }
                }
                else if (node.LocalName == "complexType")
                {
                    list = node.SelectNodes("child::*/xs:element | child::*/xs:group", this.NamespaceManager).Cast<XmlNode>().ToList();
                }
                else
                {
                    complexContent = node.SelectSingleNode("xs:complexType/xs:complexContent/xs:extension", this.NamespaceManager);
                    simpleContent = node.SelectSingleNode("xs:complexType/xs:simpleContent/xs:extension", this.NamespaceManager);

                    if (complexContent != null)
                    {
                        var baseTypeName = complexContent.Attributes["base"].Value;
                        var baseTypeNode = this.SchemaDocument.SelectSingleNode("/xs:schema/xs:complexType[@name='" + baseTypeName + "']", this.NamespaceManager);

                        baseType = new SchemaElement(this.SchemaDocument, this.NamespaceManager, baseTypeName, baseTypeNode, this, "Base Type;XmlSchemaProvider.Web.Images.Folder.png");
                        
                        this.BasedOn = baseType;

                        list = complexContent.SelectNodes("xs:element | xs:group", this.NamespaceManager).Cast<XmlNode>().ToList();

                        var baseTypeList = baseTypeNode.SelectNodes("child::*/xs:element | child::*/xs:group", this.NamespaceManager).Cast<XmlNode>().ToList();

                        // kn - YOU ARE HERE!

                        list.AddRange(baseTypeList);
                    }
                    else if (simpleContent != null)
                    {
                        // todo 

                        Debugger.Break();
                    }
                    else
                    {
                        list = node.SelectNodes("xs:complexType/child::*/xs:element | xs:complexType/child::*/xs:group", this.NamespaceManager).Cast<XmlNode>().ToList();
                    }
                }

                Action<List<XmlNode>> processGroups = null;

                processGroups = l =>
                {
                    var group = l.SingleOrDefault(c => c.LocalName == "group");

                    if (group != null)
                    {
                        var list2 = (List<XmlNode>)null;
                        var groupName = group.Attributes["ref"].Value;
                        var groupReference = this.SchemaDocument.SelectSingleNode("/xs:schema/xs:group[@name='" + groupName + "']", this.NamespaceManager);

                        list2 = groupReference.SelectNodes("child::*/xs:element | child::*/xs:group", this.NamespaceManager).Cast<XmlNode>().ToList();

                        l.Remove(group);

                        processGroups(list2);

                        list.AddRange(list2);
                    }
                };

                processGroups(list);

                foreach (XmlNode childNode in list)
                {
                    if (childNode.Attributes.Cast<XmlAttribute>().Any(a => a.Name == "ref"))
                    {
                        var refNode = childNode.Attributes["ref"].Value;

                        var childNodeReference = this.SchemaDocument.SelectSingleNode("/xs:schema/xs:element[@name='" + refNode + "']", this.NamespaceManager);
                        var name = childNodeReference.Attributes["name"].Value;

                        elements.Add(new SchemaElement(this.SchemaDocument, this.NamespaceManager, name, childNodeReference, this, childNode));
                    }
                    else
                    {
                        var name = childNode.Attributes["name"].Value;

                        elements.Add(new SchemaElement(this.SchemaDocument, this.NamespaceManager, name, childNode, this));
                    }
                }

                if (baseType != null)
                {
                    elements.Add(baseType);
                }

                if (queryWhereProperty != null && queryWhereProperty == "SchemaElement")
                {
                    elements = elements.Where(s => s.Name == (string)queryWhereValue).ToList();
                }

                return elements;
            }
        }

        public IEnumerable<IElement> ChildElements
        {
            get 
            {
                var elements = this.Elements.AsQueryable().Cast<IElement>().Select(e => e);

                return elements;
            }
        }

        public IEnumerable<IOperation> Operations
        {
            get { return null; }
        }

        [DataMember, Key]
        public string ID
        {
            get
            {
                return this.MakeID("SchemaElement='" + name + "'");
            }
        }

        [DataMember]
        public string ParentID
        {
            get
            {
                return parent.ID;
            }
        }

        [DataMember]
        public string Name
        {
            get 
            {
                return name;
            }
        }

        [DataMember]
        public string ImageURL
        {
            get
            {
                return string.Empty;
            }
        }

        [DataMember]
        public bool IsContainer
        {
            get
            {
                return false;
            }
        }

        [Include, Association("Parent_BaseType", "ID", "ParentID")]
        public BaseType DataType
        {
            get
            {
                string _namespace = null;
                string suggestedNamespace = null;
                var parent = this.Parent;

                while (parent != null)
                {
                    if (parent is Project)
                    {
                        var project = (Project)parent;
                        _namespace = project.DefaultNamespace;

                        break;
                    }
                    else if (parent is Schema)
                    {
                        var attributes = ((Schema)parent).SchemaDocument.ChildNodes.Cast<XmlNode>().Single(e => e.LocalName == "schema").Attributes.Cast<XmlAttribute>();

                        if (attributes.Any(a => a.Name == "targetNamespace"))
                        {
                            suggestedNamespace = attributes.Single(a => a.Name == "targetNamespace").Value;
                        }
                    }

                    parent = parent.Parent;
                }

                if (suggestedNamespace != null)
                {
                    var uri = new Uri(suggestedNamespace);

                    suggestedNamespace = uri.Host;

                    uri.Segments.Where(s => s.Replace("/", "").Length > 0).ToList<string>().ForEach(s => suggestedNamespace += "." + s.Replace("/", string.Empty).Replace(".xsd", ""));
                }

                return new BaseType
                {
                    FullyQualifiedName = _namespace + "." + name,
                    SuggestedNamespace = suggestedNamespace,
                    Name = name,
                    ID = name,
                    ParentID = this.ID
                };
            }
        }

        public IQueryable ExecuteWhere(string property, object value)
        {
            if (value is XPathAxisElement && (property == "ID" || property == "ParentID"))
            {
                var predicate = ((XPathAxisElement)value).Predicates.First();

                queryWhereProperty = predicate.Left;
                queryWhereValue = predicate.Right;
            }
            else
            {
                Debugger.Break();

                queryWhereProperty = property;
                queryWhereValue = value;
            }

            return this.ChildElements.AsQueryable();
        }

        public IQueryable ExecuteWhere(System.Linq.Expressions.Expression expression)
        {
            throw new NotImplementedException();
        }

        public IQueryable ExecuteWhere(XPathAxisElement element)
        {
            var predicate = element.Predicates.First();

            queryWhereProperty = predicate.Left;
            queryWhereValue = predicate.Right;

            return this.ChildElements.AsQueryable();
        }

        public void ClearPredicates()
        {
            queryWhereProperty = null;
            queryWhereValue = null;
        }

        public IRoot Root
        {
            get
            {
                IBase baseObject = this;

                while (baseObject != null)
                {
                    baseObject = baseObject.Parent;

                    if (baseObject is IRoot)
                    {
                        return (IRoot)baseObject;
                    }
                }

                return null;
            }
        }


        [DataMember]
        public string DesignComments
        {
            get 
            { 
                return string.Empty; 
            }
        }

        [DataMember]
        public string Documentation
        {
            get 
            { 
                return string.Empty; 
            }
        }

        [DataMember]
        public bool HasDocumentation
        {
            get
            {
                return false;
            }
            set
            {

            }
        }

        [DataMember]
        public string DocumentationSummary
        {
            get
            {
                return string.Empty;
            }
            set
            {

            }
        }

        [DataMember]
        public float ChildOrdinal
        {
            get
            {
                return 1;
            }
        }

        [DataMember]
        public string DebugInfo
        {
            get
            {
                return this.GetDebugInfo();
            }
        }

        public IBase Parent
        {
            get
            {
                return this.parent;
            }
        }

        [DataMember]
        public string FolderKeyPair { get; set; }

        [DataMember]
        public DefinitionKind Kind
        {
            get
            {
                return DefinitionKind.NotApplicable;
            }
        }

        [DataMember]
        public bool HasChildren
        {
            get
            {
                if (this.Elements.Any())
                {
                    return true;
                }
                else if (this.ElementAttributes.Any())
                {
                    return true;
                }

                return false;
            }
        }

        [DataMember, Include, Association("Parent_Facet", "ID", "ParentID")]
        public Facet[] Facets
        {
            get
            {
                var id = Guid.NewGuid().ToString();

                var facets = new List<Facet>()
                {
                    new Facet
                    {
                        ID = id,
                        ParentID = this.ID,
                        FacetType = new BaseType
                        {
                            ID = Guid.NewGuid().ToString(),
                            ParentID = id,
                            Name = typeof(XPathAttribute).Name,
                            FullyQualifiedName = typeof(XPathAttribute).AssemblyQualifiedName
                        },
                        Arguments = new Argument[]
                        {
                            new Argument 
                            {
                                ID = Guid.NewGuid().ToString(),
                                ParentID = id,
                                Value = "\"" + xPathExpression + "\""
                            }
                        } 
                    }
                };

                if (this.ReferencedFrom != null)
                {
                    if (this.ReferencedFrom.Attributes.Cast<XmlAttribute>().Any(a => a.LocalName == "minOccurs"))
                    {
                        // minOccurs

                        var minOccurs = this.ReferencedFrom.Attributes.Cast<XmlAttribute>().Single(a => a.LocalName == "minOccurs").Value;
                        id = Guid.NewGuid().ToString();

                        facets.Add(new Facet
                        {
                            ID = id,
                            ParentID = this.ID,
                            FacetType = new BaseType
                            {
                                ID = Guid.NewGuid().ToString(),
                                ParentID = id,
                                Name = typeof(MinOccursAttribute).Name,
                                FullyQualifiedName = typeof(MinOccursAttribute).AssemblyQualifiedName
                            },
                            Arguments = new Argument[] 
                                { 
                                    new Argument 
                                    {
                                        ID = Guid.NewGuid().ToString(),
                                        ParentID = id,
                                        Value = minOccurs
                                    } 
                                }
                        });

                    }

                    if (this.ReferencedFrom.Attributes.Cast<XmlAttribute>().Any(a => a.LocalName == "maxOccurs"))
                    {
                        // minOccurs

                        var maxOccurs = this.ReferencedFrom.Attributes.Cast<XmlAttribute>().Single(a => a.LocalName == "maxOccurs").Value;
                        id = Guid.NewGuid().ToString();

                        facets.Add(new Facet
                        {
                            ID = id,
                            ParentID = this.ID,
                            FacetType = new BaseType
                            {
                                ID = Guid.NewGuid().ToString(),
                                ParentID = id,
                                Name = typeof(MaxOccursAttribute).Name,
                                FullyQualifiedName = typeof(MaxOccursAttribute).AssemblyQualifiedName
                            },
                            Arguments = new Argument[] 
                                { 
                                    new Argument 
                                    {
                                        ID = Guid.NewGuid().ToString(),
                                        ParentID = id,
                                        Value = maxOccurs
                                    } 
                                }
                        });
                    }
                }
                else
                {
                    if (node.Attributes.Cast<XmlAttribute>().Any(a => a.LocalName == "minOccurs"))
                    {
                        // minOccurs

                        var minOccurs = node.Attributes.Cast<XmlAttribute>().Single(a => a.LocalName == "minOccurs").Value;
                        id = Guid.NewGuid().ToString();

                        facets.Add(new Facet
                        {
                            ID = id,
                            ParentID = this.ID,
                            FacetType = new BaseType
                            {
                                ID = Guid.NewGuid().ToString(),
                                ParentID = id,
                                Name = typeof(MinOccursAttribute).Name,
                                FullyQualifiedName = typeof(MinOccursAttribute).AssemblyQualifiedName
                            },
                            Arguments = new Argument[] 
                                { 
                                    new Argument 
                                    {
                                        ID = Guid.NewGuid().ToString(),
                                        ParentID = id,
                                        Value = minOccurs
                                    } 
                                }
                        });

                    }

                    if (node.Attributes.Cast<XmlAttribute>().Any(a => a.LocalName == "maxOccurs"))
                    {
                        // minOccurs

                        var maxOccurs = node.Attributes.Cast<XmlAttribute>().Single(a => a.LocalName == "maxOccurs").Value;
                        id = Guid.NewGuid().ToString();

                        facets.Add(new Facet
                        {
                            ID = id,
                            ParentID = this.ID,
                            FacetType = new BaseType
                            {
                                ID = Guid.NewGuid().ToString(),
                                ParentID = id,
                                Name = typeof(MaxOccursAttribute).Name,
                                FullyQualifiedName = typeof(MaxOccursAttribute).AssemblyQualifiedName
                            },
                            Arguments = new Argument[] 
                                { 
                                    new Argument 
                                    {
                                        ID = Guid.NewGuid().ToString(),
                                        ParentID = id,
                                        Value = maxOccurs
                                    } 
                                }
                        });
                    }
                }

                return facets.ToArray<Facet>();
            }
        }

        [Exclude]
        public ContainerType AllowableContainerTypes
        {
            get 
            {
                if (this.IsContainer)
                {
                    return ContainerType.Property;
                }
                else if (node.ChildNodes.Count == 1)
                {
                    return ContainerType.Construct | ContainerType.Property;
                }
                else
                {
                    return ContainerType.Construct;
                }
            }
        }

        [Exclude]
        public ConstructType AllowableConstructTypes
        {
            get 
            {
                if (isEnum)
                {
                    return ConstructType.Enum;
                }
                else
                {
                    return ConstructType.Class | ConstructType.Struct;
                }
            }
        }

        [DataMember]
        public string XPathExpression
        {
            get 
            {
                return xPathExpression;
            }
        }

        [Exclude]
        public ContainerType DefaultContainerType
        {
            get 
            {
                var parentIsContainer = true;
                var parentElement = parent as SchemaElement;

                if (parentElement != null)
                {
                    parentIsContainer = parentElement.IsContainer;
                }

                if (!parentIsContainer && name.IsPlural() && this.ChildElements.Count() == 1)
                {
                    return ContainerType.Property;
                }
                else
                {
                    return ContainerType.NotSet;
                }
            }
        }

        [Exclude]
        public ConstructType DefaultConstructType
        {
            get 
            {
                if (isEnum)
                {
                    return ConstructType.Enum;
                }
                else
                {
                    return ConstructType.Class;
                }
            }
        }

        public IAbstraXExtension LoadExtension()
        {
            throw new NotImplementedException();
        }

        [DataMember]
        public Modifiers Modifiers
        {
            get
            {
                return Modifiers.Unknown;
            }
        }
    }
}
